#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import gobject
import pango
import time
import gtk

import ftp
import about
import icons
import app
from gtkmisc import *


def nullifnegative(n):
    if n < 0:
        return 0
    else: return n

def get_status_icon(column, cell, model, iter):
    direction = model.get_value(iter, QueueStore.I_DIRECTION)
    status = model.get_value(iter, QueueStore.I_STATUS)
    filename = model.get_value(iter, QueueStore.I_FILENAME)
    
    pixbuf = None
    if status == QueueStore.ST_TRANSFERING:
        pixbuf = icons.from_theme('player_play', size=16)
    elif status == QueueStore.ST_WAITING:
        pixbuf = icons.by_filename(filename, size=16)
    elif status == QueueStore.ST_ERROR:
        pixbuf = icons.from_theme('error', size=16)
    elif status == QueueStore.ST_FINISHED:
        pixbuf = None
        
    cell.set_property('pixbuf', pixbuf)
    return pixbuf


class QueueStore(gtk.ListStore):
    
    I_DIRECTION = 0
    I_STATUS = 1
    I_FILENAME = 2
    I_SIZE = 3
    I_STRSIZE = 4
    I_TRANSFERRED = 5
    I_REMOTE = 6
    I_LOCAL = 7
    I_ERROR = 8
    
    DR_DOWN = 0
    DR_UP = 1
    
    ST_WAITING = 0
    ST_TRANSFERING = 1
    ST_FINISHED = 2
    ST_ERROR = 3
    
    def __init__(self):
        gtk.ListStore.__init__(self, 
            int, int, str, int, str, int, object, str, str)
        self.update_size()
        
    def update_size(self, add=None):
        """Sets self.total_size. If add is set, just add it to total_size."""
        if add:
            self.total_size += add
        else:
            c = 0
            for it in self.iters():
                c += self.get_value(it, self.I_SIZE)
            self.total_size = c
            
        app.debug(
            "Total queue size: %s" % units(self.total_size),
            "queue"
        )
        return self.total_size
        
    def transferred(self):
        """Returns how many bytes in the queues are transferred."""
        total = 0
        for i in self.iters():
            total += self.get_value(i, self.I_TRANSFERRED)
        return total
    
    def append(self, item, local, direction=DR_DOWN, status=ST_WAITING):
        """Append a FileItem."""
        size = item.isfile() and units(item.size) or ""
        list = [direction, status, unic(item.filename),
            item.size, size, 0, item, local, '']
        if direction == self.DR_DOWN:
            if os.path.isfile(local):
                transferred = os.path.getsize(local)
                list[QueueStore.I_TRANSFERRED] = transferred
                if transferred == list[QueueStore.I_SIZE]:
                    list[QueueStore.I_STATUS] = QueueStore.ST_FINISHED
        try:
            gtk.ListStore.append(self, list)
        except TypeError, e:
            print "ERROR! list was:"
            print list
            return False
        self.update_size(add=item.size)
            
    def iters(self):
        """Yield iterators, top-down."""
        iter = self.get_iter_first()
        while 1:
            if not iter:
                break
            next = self.iter_next(iter)
            yield iter
            iter = next
            
    def liters(self):
        return list(self.iters())
            
    def get_waiting_iter(self):
        """Returns iterator to next waiting item."""
        for i in self.iters():
            if self.get_value(i, self.I_STATUS) == self.ST_WAITING:
                return i
                
    def clear_finished(self):
        """Removes finished transfers."""
        for i in self.iters():
            if self.get_value(i, self.I_STATUS) == self.ST_FINISHED:
                self.remove(i)
        self.update_size()
    
    def clear(self):
        gtk.ListStore.clear(self)
        self.update_size()


def queue_from_disk(fn):
    """Creates a QueueStore from disc.
    
    fn is the name of the queue in queues_dir.
    """
    fd = open(app.queues_dir + os.path.sep + fn, 'r')
    q = QueueStore()
    
    for i in fd.readlines():
        i = i.strip()
        list = i.split(';')
        list[QueueStore.I_DIRECTION] = int(list[QueueStore.I_DIRECTION])
        list[QueueStore.I_STATUS] = int(list[QueueStore.I_STATUS])
        list[QueueStore.I_SIZE] = int(list[QueueStore.I_SIZE])
        list[QueueStore.I_TRANSFERRED] = int(list[QueueStore.I_TRANSFERRED])
        list[QueueStore.I_REMOTE] = ftp.remotefile_from_str(list[QueueStore.I_REMOTE])
        gtk.ListStore.append(q, list)
        
    fd.close()
    q.update_size()
    return q


def serialize(store):
    """Creates multi-line text of a queue. Each line is I_* 
    separated by the ; character."""
    items = []
    for i in store.iters():
        ml = [str(store.get_value(i, k)) for k in xrange(9)]
        ml[QueueStore.I_REMOTE] = store.get_value(i, QueueStore.I_REMOTE).allstr()
        items.append(";".join(ml))
    return "\n".join(items)
    

class QueueWindow:
    _widgets = ['tree', 'statusbar', 'expander', 'menuitem_run', 'progressbar']
    
    def __init__(self, appinst, name="Queue", store=None):
        self.appinst = appinst
        self.config = appinst.config
        self.name = name
        
        # get widgets
        self.wTree = gtk.glade.XML(app.glade_file, "queue_window")
        for widget in self._widgets:
            setattr(self, widget, self.wTree.get_widget(widget))
        self.window = self.wTree.get_widget('queue_window')
        
        # connect signals
        _signals = {
            'imagemenuitem_about_activate':
                lambda w: about.AboutDialog().run(),
            'menuitem_close_activate':
                lambda w: self.window.destroy(),
            'queue_window_destroy':
                lambda w: self.appinst.close_window(self),
            'menuitem_run_toggled': self.toggle_run,
            'menuitem_clear_activate':
                lambda w: (self.store.clear_finished(), self.save()),
            'menuitem_clear_all_activate':
                lambda w: (self.store.clear(), self.save()),
            'menuitem_connect_activate':
                lambda w: self.appinst.new_connect_dialog(),
            'queue_window_resized': self.window_resized,
            'queue_tree_button_press_event':
                self.queue_button_event,
            'menuitem_rename_activate': lambda w: self.get_new_name(),
        }
        self.wTree.signal_autoconnect(_signals)
        
        if store:
            self.store = store
        else:
            self.store = QueueStore()
        self.tree.set_model(self.store)
        
        # enable multi-selection
        self.tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        
        for t in ("name", "transferred", "size", "from", "to", "error"):
            c = gtk.CellRendererText()
            setattr(self, "cell_%s" % t, c)
            
        self.cell_progress = gtk.CellRendererProgress()
        self.cell_status = gtk.CellRendererPixbuf()
        
        column_name = gtk.TreeViewColumn("Name")
        column_name.pack_start(self.cell_status, False)
        column_name.pack_start(self.cell_name, True)
        column_size = gtk.TreeViewColumn("Progress")
        column_size.pack_start(self.cell_transferred, False)
        column_size.pack_start(self.cell_progress, False)
        column_size.pack_start(self.cell_size, False)
        column_from = gtk.TreeViewColumn("From")
        column_from.pack_start(self.cell_from, True)
        column_to = gtk.TreeViewColumn("To")
        column_to.pack_start(self.cell_to, True)
        column_error = gtk.TreeViewColumn("Error")
        column_error.pack_start(self.cell_error, False)
        
        column_name.set_attributes(self.cell_name, text=QueueStore.I_FILENAME)
        column_name.set_cell_data_func(self.cell_status, get_status_icon)
        column_name.set_expand(True)
        
        column_size.set_attributes(self.cell_size, text=QueueStore.I_STRSIZE)
        column_size.set_cell_data_func(self.cell_transferred, 
            lambda c, ce, m, i: ce.set_property('text', 
                unic(units(m.get_value(i, QueueStore.I_TRANSFERRED)))
                )
        )
        column_size.set_expand(False)
                
        column_from.set_cell_data_func(self.cell_from,
            lambda c, ce, m, i: ce.set_property('text', m.get_value(i, m.I_DIRECTION) == m.DR_DOWN and str(m.get_value(i, m.I_REMOTE)) or str(m.get_value(i, m.I_LOCAL))))
        column_from.set_expand(True)
        column_to.set_cell_data_func(self.cell_to,
            lambda c, ce, m, i: ce.set_property('text', m.get_value(i, m.I_DIRECTION) == m.DR_DOWN and str(m.get_value(i, m.I_LOCAL)) or str(m.get_value(i, m.I_REMOTE))))
        column_to.set_expand(True)
        
        column_size.set_cell_data_func(self.cell_progress,
            lambda c, ce, m, i: ce.set_property('value',
                100*m.get_value(i, m.I_TRANSFERRED)/m.get_value(i, m.I_SIZE))
        )
        column_error.set_attributes(self.cell_error, text=QueueStore.I_ERROR)
        
        self.tree.append_column(column_name)
        self.tree.append_column(column_size)
        self.tree.append_column(column_from)
        self.tree.append_column(column_to)
        self.tree.append_column(column_error)
        
        # set window size
        self.window.set_default_size(
            self.config.getint("queue", "width"),
            self.config.getint("queue", "height")
        )
        
        # Initialize FTP Thread
        self.ftp = ftp.FTPThread(daemon=True)
        _callbacks = {}
        for cb in ftp.FTPThread._all_callbacks:
            if hasattr(self, "ftp_"+cb):
                _callbacks[cb] = getattr(self, "ftp_"+cb)
        self.ftp.add_callbacks(_callbacks)

        # These contain the item and iter of which is currently being processed
        self._item = None
        self._iter = None
        
        self.window.set_icon_from_file(os.path.join(app.data_dir, "flash-64.png"))
        self.window.set_title(self.name)
        self.window.show_all()
        
    def close(self):
        """Finish up, destroy window. Called by app."""
        if self.ftp.busy():
            self.ftp.abor = True
            self.ftp.quit()
        self.ftp.join()
        self.window.destroy()
        
    def save(self):
        if self.store.total_size == 0:
            if os.path.isfile(os.path.join(app.queues_dir, self.name)):
                os.remove(os.path.join(app.queues_dir, self.name))
        else:
            fd = open("%s%s%s" % (app.queues_dir, os.path.sep, self.name), "w")
            fd.write(serialize(self.store))
            fd.close()
        
    def toggle_run(self, checkitem):
        if checkitem.get_active():
            self.pop()
        else:
            self.stop_queue()
        
    def window_resized(self, window):
        width, height = window.get_size()
        self.config.set("queue", "width", str(width))
        self.config.set("queue", "height", str(height))
        
    def stop_queue(self):
        """Stops the queue."""
        if self.ftp.busy():
            self.ftp.abor = True
            self.ftp.jobs = []
        if self._iter: # set current file to 'waiting'
            self.store.set_value(self._iter, QueueStore.I_STATUS, QueueStore.ST_WAITING)
            self._iter, self._item = None, None

    def append_item(self, fr, to, status=0):
        """Appends one item to the queue."""
        if isinstance(fr, ftp.RemoteFile) and isinstance(to, str): # a download
            self.store.append(fr, to, direction=0, status=status)
            app.debug("Adding download item: %s to %s" % (fr, to), "queue")
        elif isinstance(to, ftp.RemoteFile) and isinstance(fr, str): # upload
            self.store.append(to, fr, direction=1, status=status)
            app.debug("Adding upload from %s to %s" % (fr, to), "queue")
        else:
            raise ValueError, "Unknown from/to types. fr: %s to: %s" % \
                (type(fr), type(to))
        self.save()
        if self.menuitem_run.get_active() and not self.ftp.busy():
            app.debug("Popping item since queue is not running.", "queue")
            self.pop()
        
    def pop(self, *args):
        """Pop an item. 
        
        Should probably not be started while queue is running.
        Returns True if there queue was started, False if there were no
        items to be transferred.
        """
        iter = self.store.get_waiting_iter()
        
        if iter:
            # set status to 'transfering'
            self.store.set_value(iter, QueueStore.I_STATUS, 1)
            # remember item and iter locally
            self._item = self.store.get_value(iter, QueueStore.I_REMOTE)
            self._iter = iter
            # get local path
            local = self.store.get_value(iter, QueueStore.I_LOCAL)
            
            direction = self.store.get_value(iter, QueueStore.I_DIRECTION)
            if direction == QueueStore.DR_DOWN:
                # create dir if it doesn't exist
                if os.path.sep in local and not os.path.isdir(os.path.dirname(local)):
                    os.makedirs(os.path.dirname(local))
                # now transfer it
                app.debug("Downloading %s to %s" % (self._item.filename, local), "queue")
                self.set_statusbar("Downloading")
                self.ftp.download(self._item, local, rest=True)
            elif direction == QueueStore.DR_UP:
                app.debug("Uplading %s to %s" % (local, self._item.filename))
                self.set_statusbar("Uploading")
                self.ftp.upload(local, self._item)
            return True
        else:
            return False
            
    def queue_button_event(self, widget, event):
        iters = iters_selection(self.tree.get_selection())
        if len(iters) == 0: 
            return
            
        def move_to_top(iters):
            for iter in iters[::-1]:
                self.store.move_before(iter, self.store.liters()[0])
            self.save()
            
        def move_to_bottom(iters):
            for iter in iters:
                self.store.move_after(iter, self.store.liters()[-1])
            self.save()
            
        def remove(iters):
            for iter in iters:
                self.store.remove(iter)
            self.save()
        
        if event.button == 3:
            menu = gtk.Menu()
            
            mt = gtk.ImageMenuItem(stock_id=gtk.STOCK_GOTO_TOP)
            mt.connect('activate', lambda w: move_to_top(iters))
            
            mb = gtk.ImageMenuItem(stock_id=gtk.STOCK_GOTO_BOTTOM)
            mb.connect('activate', lambda w: move_to_bottom(iters))
            
            mr = gtk.ImageMenuItem(stock_id=gtk.STOCK_REMOVE)
            mr.connect('activate', lambda w: remove(iters))
            
            menu.append(mt)
            menu.append(mb)
            menu.append(mr)
            menu.show_all()
            menu.popup(None, None, None, event.button, event.time)
            
    def set_statusbar(self, message):
        context_id = self.statusbar.get_context_id("status")
        self.statusbar.pop(context_id)
        self.statusbar.push(context_id, message)
        
    def set_progress(self):
        """Update window title and progressbar with values from self.store"""
        done = self.store.transferred() * 1.0 / self.store.total_size
        
        self.progressbar.set_fraction(done)
        self.window.set_title("%s - %i%%" % (self.name, done*100))
        
    def get_new_name(self):
        """Open a dialog for new name."""
        dtree = gtk.glade.XML(app.glade_file, 'dialog_rename_queue')
        w = dtree.get_widget('dialog_rename_queue')
        cancel = dtree.get_widget('button_cancel')
        ok = dtree.get_widget('button_ok')
        entry = dtree.get_widget('entry_name')
        
        entry.set_text(self.name)
        cancel.connect('clicked', lambda i: w.destroy())
        def set_name(): 
            self.name = entry.get_text()
            self.window.set_title(self.name)
        ok.connect('clicked', lambda i: (set_name(), w.destroy()))
        
        w.show_all()
        w.run()
        
    def ftp_received(self, data):
        app.debug(data, "queue-ftp")
        
    def ftp_sending(self, data):
        app.debug(data, "queue-ftp")
            
    @threads
    def ftp_ssl_enabled(self, server, issuer):
        self.set_statusbar("SSL has been enabled")

    @threads    
    def ftp_authenticated(self, r):
        self.set_statusbar("Connected, Authenticated")
        self.ftp.cwd(self._item.dir)
        
    @threads
    def ftp_connected(self, msg):
        self.set_statusbar("Connected")
    
    @threads    
    def ftp_authentication_error(self, e):
        self.set_statusbar("Authentication err: " + str(e))
    
    @threads    
    def ftp_cwd_error(self, msg):
        app.debug("Could not CWD to directory!", "queue")
    
    @threads        
    def ftp_transfer_finished(self, cmd):
        self.set_statusbar("Transfer finished")
        # update transferred in store
        self.store.set_value(self._iter, QueueStore.I_TRANSFERRED,
            self.store.get_value(self._iter, QueueStore.I_SIZE))
        
        self._item = None
        # change item to finished in the liststore
        self.store.set_value(self._iter, QueueStore.I_STATUS, QueueStore.ST_FINISHED)
        # move it to bottom
        if self.config.getboolean("queue", "finished_files_to_bottom"):
            self.store.move_after(self._iter, list(self.store.iters())[-1])
        self._iter = None
        
        self.set_progress()
        self.save()
        self.pop()
    
    def ftp_unhandled_exception(self, job, exception):
        app.debug("Unhandled exception in FTP thread: Job: %s Exception: %s" % \
            (job, exception), "queue")
        
    @threads
    def ftp_transfer_aborted(self, cmd):
        self.set_statusbar("Transfer aborted")
        self.set_progress()
    
    last_t = time.time()
    last_p = 0
    @threads
    def ftp_retr_pos(self, pos, cmd):
        t = time.time()
        if t-self.last_t < 1 or pos == -1: return
        else:
            self.set_statusbar("Downloading %s/s" % (
                units((nullifnegative(pos-self.last_p))/(t-self.last_t)))
            )
            self.last_t = t
            self.last_p = pos
            
            # update transferred bytes in store
            self.store.set_value(self._iter, QueueStore.I_TRANSFERRED, pos)
            
            self.set_progress()
            
    @threads
    def ftp_download_error(self, error):
        self.store.set_value(self._iter, QueueStore.I_STATUS, QueueStore.ST_ERROR)
        self.store.set_value(self._iter, QueueStore.I_ERROR, error)
        
    @threads
    def ftp_stor_error(self, error):
        self.store.set_value(self._iter, QueueStore.I_STATUS, QueueStore.ST_ERROR)
        self.store.set_value(self._iter, QueueStore.I_ERROR, error)
    
